{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 问题：进程、线程、协程对比\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "面试官想从这道题中考察面试人什么？\n",
    "并发编程是一个高级编程问题，与之相关的面试题包括：多线程并发，多进程并发，协程并发。这里面牵扯了很多基本概念，但又是很容易让人混淆的。概念都不清楚，那还谈什么优化性能。\n",
    "\n",
    "答案\n",
    "进程：一个运行的程序（代码）就是一个进程，没有运行的代码叫程序，进程是系统资源分配的最小单位，进程拥有自己独立的内存空间，所有进程间数据不共享，开销大；\n",
    "线程: cpu 调度执行的最小单位，也叫执行路径，不能独立存在，依赖进程存在，一个进程至少有一个线程，叫主线程，而多个线程共享内存（数据共享，共享全局变量)，从而极大地提高了程序的运行效率。线程的调度是由操作系统负责；\n",
    "协程: 是一种用户态的轻量级线程，协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度时，将寄存器上下文和栈保存到其他地方，在切回来的时候，恢复先前保存的寄存器上下文和栈，直接操中栈则基本没有内核切换的开销，可以不加锁的访问全局变量，所以上下文的切换非常快。\n",
    "拓展\n",
    "在多线程任务中，一个线程可能被阻塞并正在等待某些东西，而同一任务中的另一个线程可能正在运行。但是，如果某个进程阻塞，则整个进程将停止。比如，在同一个 web 浏览器中，我们想要同时下载文件，查看动画和打印某些内容时，这个时候多线程处理很有用，它可以多个线程来回切换，一个线程等待，而另一个线程可能仍在运行，那么整个进程不会停止。操作系统将其进程分开并分配所需的资源，这样它们就不太可能相互干扰并导致系统故障（例如死锁或抖动）。操作系统可以提供用于进程间通信的机制，从而以安全且可预测的方式启用进程。\n",
    "\n",
    "在单处理器上，多线程通常通过时分多路复用发生，即单处理器在不同线程之间切换。这种上下文切换发生得足够快，因此我们可以感觉到线程正在同时运行。在多处理器和多核系统上，线程可以真正并发，每个 CPU 同时（并发和并行）执行一个单独的线程。\n",
    "\n",
    "并发 vs 并行\n",
    "很多人错认为并发和并行是一个概念，但其实不是，一个是看上去是多个任务同时进行，另一个是真正的同时执行。\n",
    "\n",
    "并发：多个线程被一个 cpu 轮流切换着执行，表示多种任务可以同时存在；\n",
    "并行：多个线程被多个 CPU 同时进行，表示多种任务可以同时进行，从物理上就可以多个任务一起执行，它们的执行时间与 CPU 个数相关。\n",
    "Tips : 对于操作系统来说，一个任务就是一个进程，每个进程至少要干一件事，所以，一个进程至少有一个线程。操作系统在多个线程之间快速切换，让每个线程都短暂地交替运行，看起来就像同时执行一样，所以，这是并发。而真正的并行执行多任务只能在多核 CPU 上实现。\n",
    "标准库为我们提供了 concurrent.futures 模块，它提供了 ThreadPoolExecutor 和 ProcessPoolExecutor 两个类，实现了对 threading 和 multiprocessing 的进一步抽象，对编写线程池 / 进程池提供了直接的支持。\n",
    "\n",
    "适用场景\n",
    "多进程：密集 CPU 任务，需要充分使用多核 CPU 资源（服务器，大量的并行计算）的时候，用多进程。\n",
    "缺陷：多个进程之间通信成本高，切换开销大。\n",
    "\n",
    "多线程：密集 I/O 任务（网络 I/O，磁盘 I/O，数据库 I/O）使用多线程合适。\n",
    "缺陷：请求返回的数据是无序的，同一个时间切片只能运行一个线程，不能做到高并行，但是可以做到高并发。\n",
    "\n",
    "协程：又称微线程，在单线程上执行多个任务，用函数切换，开销极小。不通过操作系统调度，没有进程、线程的切换开销。协程返回的数据是有序的。\n",
    "缺陷：单线程执行，处理密集 CPU 和本地磁盘 IO 的时候，性能较低。处理网络 I/O 性能还是比较高。\n",
    "\n",
    "面试官追问：Python 中怎么使用协程并发\n",
    "协程可以干什么？\n",
    "\n",
    "协程是一种计算机程序，可以对子例程进行概括，以允许多个入口点在某些位置挂起和恢复执行。协程适合于实现协作任务，迭代器，无限列表和管道。\n",
    "\n",
    "协程也是一种线程，线程由操作系统决定什么时候使用 CPU，什么时候让出 CPU。如果我们能够自行调度自己写的程序，让一些代码块遇到 IO 操作时（因为 IO 操作不占用 CPU，且比较耗时），切换去执行另外一些需要 CPU 操作的代码块。协程是一种常用的并发手段，比如，协程之间可以链接在一起以形成管道，一个协程可能会消耗输入数据并将其发送给其他协程处理程序。最后可能会有另外一个协程显示结果。\n",
    "\n",
    "Tips: 协程就是你可以暂停执行的函数。\n",
    "\n",
    "协程是怎么来的？从 yield 说起。\n",
    "\n",
    "在 Python 中，yield 函数在协程中发挥了重要的作用。我们通过前面的一些介绍，对生成器也有了大概的了解，如果可以利用生成器挂起，并且再让它拥有 “将东西发送回生成器” 的功能，这就是 Python 里协程最初的概念：将生成器当做某种任务并将任务协作切换。具体的例子可以参加 “问题：生成器和迭代器” 关于 yield from iterator 的拓展部分，这里就不赘述了。协程为常规数据处理机制提供了强大的替代方案。可以轻松地组合，修改和重写代码单元，同时在整个生命周期中受益于可变的持久性。后面还会介绍使用 async/await 实现协程。\n",
    "\n",
    "协程的优势\n",
    "\n",
    "协程的数量理论上可以是无限个，而且没有线程之间的切换动作，执行效率比线程高；\n",
    "协程不需要 “锁” 机制，即不需要 lock 和 release 过程，因为所有的协程都在一个线程中；\n",
    "相对于线程，协程更容易调试 debug，因为所有的代码是顺序执行的。\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
